package org.deep;

import org.deep.cmd.*;

import java.util.Queue;

public enum CmdManager {
    INSTANCE;
    private final Object lock = new Object();

    private IPrint iPrint;

    public void SetPrintCallback(IPrint iPrint) {
        this.iPrint = iPrint;
    }

    public <T> T send(CmdSource source, SingleCmd<T> cmd) throws Exception {
        synchronized (lock) {
            source.queue.clear();
            // 重发
            for (int i = 0; i < cmd.reSendTimes + 1; i++) {
                try {
                    if (i == 0) {
                        printInfo(String.format("%s: send =>\n%s", cmd.getClass().getName(), cmd.sendConentToHexString()));
                    } else {
                        printInfo(String.format("%s: resend %d =>\n%s", cmd.getClass().getName(), i, cmd.sendConentToHexString()));
                    }
                    // 发送指令
                    source.write(cmd.send());
                    // 在超时时间内，等待接收数据
                    byte[] receiveBuf = receiveDataWithTimeOut(source.queue, cmd.getClass().getName(), cmd.receiveSize, cmd.reSendInterval);
                    // 校验并解析数据
                    return cmd.handleWithCheck(receiveBuf);
                } catch (Exception e) {
                    // 打印错误
                    if (i == 0) {
                        printError(String.format("%s: send => %s %s", cmd.getClass().getName(), System.lineSeparator(), cmd.sendConentToHexString()), e);
                    } else {
                        printError(String.format("%s: resend %d => %s %s", cmd.getClass().getName(), i, System.lineSeparator(), cmd.sendConentToHexString()), e);
                    }
                    // 未超过重发次数，继续重发
                    if (i < cmd.reSendTimes) {
                        continue;
                    }
                    // 重发次数耗尽，抛出异常
                    throw e;
                }
            }
            // 正确逻辑不会执行到此
            throw new Exception("Useless");
        }
    }

    private byte[] receiveDataWithTimeOut(Queue<byte[]> queue, String cmdName, int receiveSize, int waitTime) throws InterruptedException, TimeOutException {
        long startTime = System.currentTimeMillis();
        byte[] receiveBuf = new byte[receiveSize];
        int copyCount = 0;
        // 在指定时间内拷贝指定长度数据至receiveBuf
        while (copyCount < receiveSize && (System.currentTimeMillis() - startTime) < waitTime) {
            if (queue.isEmpty()) {
                Thread.sleep(100);
            } else {
                byte[] data = queue.poll();
                if (data != null) {
                    int count;
                    printInfo(String.format("%s: receive %d bytes =>\n %s", cmdName, data.length, ByteUtil.conentToHexString(data, true, 10)));
                    if (copyCount + data.length > receiveSize) {
                        System.arraycopy(data, 0, receiveBuf, copyCount, receiveSize - copyCount);
                        count = receiveSize - copyCount;
                    } else {
                        System.arraycopy(data, 0, receiveBuf, copyCount, data.length);
                        count = data.length;
                    }
                    copyCount += count;
                    // 打印缓冲区已拷贝的数据
                    byte[] temp = new byte[copyCount];
                    System.arraycopy(receiveBuf, 0, temp, 0, copyCount);
                    printInfo(String.format("%s: already copy %d bytes =>\n %s", cmdName, copyCount, ByteUtil.conentToHexString(temp, true, 10)));
                }
            }
        }
        // 超时则抛出异常
        if ((System.currentTimeMillis() - startTime) > waitTime) {
            throw new TimeOutException();
        }
        return receiveBuf;
    }

    private void printInfo(String msg) {
        if (iPrint == null) {
            System.out.println(msg);
            return;
        }
        iPrint.printInfo(msg);
    }

    private void printError(String msg, Exception e) {
        if (iPrint == null) {
            System.out.printf("Msg =>%s%sException =>%s", msg, System.lineSeparator(), e);
            return;
        }
        iPrint.printError(msg, e);
    }

    public interface IPrint {
        public void printInfo(String msg);

        public void printError(String msg, Throwable e);
    }
}
